var express = require('express'); // 引入Http服务
var path = require('path');
var bodyParser = require('body-parser');

var NoteBook = require('../model/NoteBook');
var BSMsg = require('../model/BSMsg');
var NodeMsg = require('../model/NodeMsg');

var AppUtils = require('websocket_p2pnet').AppUtils;
var WSNode = require('websocket_p2pnet').WSNode;

/**
 * 区块账本 Web 程序（入口程序）
 */
class BlockWeb {
  /**
   * 构造函数，创建各种内部对象变量
   * 完成注册路由，并准备开启 http 服务
   */
  constructor() { 
    var webConfigPath = path.join(__dirname,'./WebConfig.json');
    this.appUtils = AppUtils.getSingle(webConfigPath);
    this.noteBook = NoteBook.getSingle();
    this.wsNode = null;

    // 静态文件夹路径 ， __dirname 为运行程序(BlockWeb.js)的目录
    this.staticPagePath = path.join(__dirname, '../static');
    // 1. 创建 http 服务
    this.app = express();
    // 2. 注册 事件
    this.regWebEventPipe();
    // 3. 注册 网站路由
    this.regWebRoute();
  }

  /**
   * 2.0 注册 Web事件 管道
   */
  regWebEventPipe() {
    //设置 bodyParser，方便用对象方式获取 post 数据
    this.app.use(bodyParser.json());
    this.app.use(bodyParser.urlencoded({ extended: false }));

    //设置 网站静态文件目录
    this.app.use(express.static(this.staticPagePath));

    // 浏览器请求日志记录
    this.app.use(function (req, res, next) { 
      var msg = '---[浏览器' + AppUtils.getSingle().getClientIp(req) + ']正在请求[Web.' + req.originalUrl + ']-------';
      console.log(msg);
      next();
    });

    // 异常处理
    // this.app.use(function(err,req,res,next) {
    //   console.log("Error happens",err.stack);
    // });
  }

  /**
   * 3.0 注册 网站 路由
   */
  regWebRoute() { 
    this.app.get('/',this.rootPage);
    this.app.get('/getNoteBookData',this.getNoteBookData);
    this.app.post('/addGenesis', this.addGenesis);
    this.app.post('/addNote', this.addNote);
    this.app.post('/checkNoteData', this.checkNoteData);
    this.app.post('/showNodeNet', this.showNodeNet);

    this.app.post('/startWSServer', this.startWSServer);
    this.app.post('/joinNodeNet', this.joinNodeNet);
    this.app.post('/reqBlockData', this.reqBlockData);
  }

  // -------------------------------一、web页面操作-----------------------
  // 3.1 路由：根目录
  rootPage(req, res) { 
    res.send('<h1>黑马币系统~~</h1>');
  }

  // 3.2 路由：获取区块账本数据
  getNoteBookData(req, res) {
    res.send(BlockWeb.getSingle().noteBook.getListJson());
  }

  // 3.3 路由：添加创世区块
  addGenesis(req, res) {
    var bsMsg = new BSMsg();
    try {
      var firstBlock = BlockWeb.getSingle().noteBook
        .addGenesis(req.body.blockContent);
      bsMsg.SetStateAndContent(BSMsg.StateType.Ok, firstBlock);
    } catch (error) {
      BlockWeb.getSingle().appUtils.logger.logSContent('addGenesis', error.message);
      bsMsg.SetStateAndContent(BSMsg.StateType.Fail, '错误：' + error.stack);
    }
    res.send(bsMsg);
  }

  // 3.4 路由：添加普通区块
  addNote(req, res) {
    var bsMsg = new BSMsg();
    try {
      var newBlock = BlockWeb.getSingle().noteBook
        .addNote(req.body.blockContent);
      
      // 封装数据到 节点消息对象
      var nodeMsg = new NodeMsg(NodeMsg.ActionType.BroadNewBlockData, newBlock);
      // 把添加的区块 广播给 其它节点
      BlockWeb.getSingle().wsNode.broadBlockData(nodeMsg);

      bsMsg.SetStateAndContent(BSMsg.StateType.Ok, newBlock);
    } catch (error) {
      BlockWeb.getSingle().appUtils.logger.logSContent('addNote', error.message);
      bsMsg.SetStateAndContent(BSMsg.StateType.Fail, '错误：' + error.stack);
    }
    res.send(bsMsg);
  }

  // 3.5 路由：校验区块数据
  checkNoteData(req, res) { 
    var checkRes = BlockWeb.getSingle().noteBook.check();
    var isOk = (checkRes.length == 0) ? BSMsg.StateType.Ok : BSMsg.StateType.Fail;
    var bsMsg = new BSMsg(isOk, checkRes);
    res.send(bsMsg);
  }

  // 3.6 获取最新的在网节点列表
  showNodeNet(req, res) {
    var bsMsg = new BSMsg();
    try {
      var nodeArr = AppUtils.getSingle().onlineNodeList.toUriStrArray();
      bsMsg.SetStateAndContent(BSMsg.StateType.Ok, nodeArr);
    } catch (error) {
      bsMsg.SetStateAndContent(BSMsg.StateType.Fail, '获取节点列表失败~！' + error.message);
      BlockWeb.getSingle().appUtils.logger.logSContent('showNodeNet', error.stack);
    }
    res.send(bsMsg);
  }

  /**
   * 4.0 启动web服务
   * @param httpPort 网站端口号，如果不存在，则使用配置文件中的端口
   */
  startWeb(httpPort) {
    if (httpPort)
      this.appUtils.webIpAddress.port = httpPort;
    
    this.app.listen(this.appUtils.webIpAddress.port);
    this.appUtils.logger.logSTitle('startWeb', '黑马币系统 start ~~~ !' + this.appUtils.webIpAddress.getURI());
  }

  // --------------------------二、WebSocket 业务操作-----------------------------
  
  // 5.1 路由：启动 WS 服务
  startWSServer(req, res) {
    // 5.1.0 准备浏览器消息对象
    var bsMsg = new BSMsg();
    // 5.1.1 获取 WS服务端 端口号，用于启动 WS服务端监听
    var nodePort = req.body.nodePort;
    try {
      if (nodePort && nodePort.length > 0) {
        // 5.1.2 将 ws 端口 存入 appUtils
        AppUtils.getSingle().wsIpAddress.port = nodePort;

        // 5.1.3 创建 wsNode，设置用户消息事件函数，启动服务
        var blockWeb = BlockWeb.getSingle();

        blockWeb.wsNode = WSNode.getSingle(AppUtils.getSingle().wsIpAddress);
        blockWeb.wsNode.setOnMsgEvent(blockWeb.onServerMsg, blockWeb.onClientMsg);
        blockWeb.wsNode.startWork();

        // 5.1.4 设置成功消息
        bsMsg.SetStateAndContent(BSMsg.StateType.Ok, '节点【' + AppUtils.getSingle().wsIpAddress.getURI() + '】启动 WS 服务~！');
      } else {
        bsMsg.SetStateAndContent(BSMsg.StateType.Fail, '注册失败：当前端口【' + nodePort + '】在运行服务已完成注册 或 缺少端口参数~！');
      }
    } catch (error) {
      // 设置失败消息
      bsMsg.SetStateAndContent(BSMsg.StateType.Fail, error.message);
      BlockWeb.getSingle().appUtils.logger.logSContent('showNodeNet', error.stack);
    }
    res.send(bsMsg);
  }

  // 5.2 申请加入节点网络
  joinNodeNet(req, res) {
    var bsMsg = new BSMsg();
    var nodeIp = req.body.ip;
    var nodePort = req.body.port;
    try {
      // 连接远程在网节点获取在网节点列表（判断：当前服务已启动，远程节点不为空）
      if (nodeIp && nodeIp.length > 0 && nodePort && nodePort.length > 0) {

        BlockWeb.getSingle().wsNode.joinNodeNet(nodeIp,nodePort);
        //连接成功后:将自动将该连接添加到 client 连接集合中，获取在网节点列表，同时入网连接
        
        bsMsg.SetStateAndContent(BSMsg.StateType.Ok, '连接远程节点【' + nodePort + '】成功~！');
      } else { 
        bsMsg.SetStateAndContent(BSMsg.StateType.Fail,'注册失败：当前在运行服务已完成注册 或 缺少端口参数~！');
      }
    } catch (error) {
      bsMsg.SetStateAndContent(BSMsg.StateType.Fail, '注册失败~！' + error.message);
      BlockWeb.getSingle().appUtils.logger.logSContent('showNodeNet', error.stack);
    }
    res.send(bsMsg);
  }

  // 5.3 请求 其它节点请求 最新区块账本数据
  reqBlockData(req, res) {    
    // 准备 发给浏览器的数据对象
    var bsMsg = new BSMsg(BSMsg.StateType.Ok, '后台更新操作进行中...');

    // 准备 发送给其它节点的数据对象
    var nodeMsg = new NodeMsg(NodeMsg.ActionType.RequestBlockData, '请求同步一份最新的区块链账本数据！');

    // 只给第一个连接上的节点客户端 发送 请求
    if (BlockWeb.getSingle().wsNode.getServerConnCount() > 0) {
      //AppUtils.getSingle().clientConnectionList.clientConnList[0].sendMsg(nodeMsg.toJson());
      BlockWeb.getSingle().wsNode.getServerConnByIndex(0).sendUTF(nodeMsg.toJson());
    } else { 
      bsMsg.SetStateAndContent(BSMsg.StateType.Fail, '本节点尚未进入节点网络~~');
    }
    res.send(bsMsg);
  }

  //-----------------------------WS事件处理-------------------------------

  /**
   * 7.1 服务端消息处理函数
   * @param msg 
   * @param connObj 
   * @param wsServerObj 
   */
  onServerMsg(msg, connObj, wsServerObj) { 
    var nodeMsg = NodeMsg.json2NodeMsgObj(msg);
    // 收到 向某个节点客户端请求的 最新区块账本数据
    if (nodeMsg.actionType === NodeMsg.ActionType.RequestBlockData) {
      
      BlockWeb.getSingle().appUtils.logger.logSContent('onServerMsg', '接收到某个节点 发来的 区块链数据：', nodeMsg.content);

      // 对比更新自己的账本
      BlockWeb.getSingle().noteBook.updateEntireBook(nodeMsg.content);
    }
  }

  /**
   * 7.2 客户端消息处理函数
   * @param msg 
   * @param connObj 
   * @param clientObj 
   */
  onClientMsg(msg, connObj, clientObj) {
    var nodeMsg = NodeMsg.json2NodeMsgObj(msg);

      this.appUtils.logger.logCContent('onClientMsg', '收到内容：', nodeMsg.content);

    // 返回 某节点服务端 想要最新的 区块链数据
    if (nodeMsg.actionType == NodeMsg.ActionType.RequestBlockData) {
      // 发送 区块链数据
      var nodeMsg = new NodeMsg(NodeMsg.ActionType.RequestBlockData, BlockWeb.getSingle().noteBook.getListJson());
      connObj.sendUTF(nodeMsg.toJson());

      this.appUtils.logger.logCContent('onClientMsg', '返回 某节点服务端 想要最新的 区块链数据 完毕~', nodeMsg);
    }

    // 收到其它节点新增成功的一个区块
    else if (nodeMsg.actionType === NodeMsg.ActionType.BroadNewBlockData) {
      var newBlock = nodeMsg.content;
      // 验证，并添加到链中
      var res = NoteBook.getSingle().addNoteBlock(newBlock);

      if (res)
        this.appUtils.logger.logCContent('onClientMsg', '同步其它节点发来的新区块成功');
    }
  }

  /**
   * 0.0 获取Web服务单例对象
   */
  static getSingle() { 
    if (!this.onlyBlockWeb) {
      this.onlyBlockWeb = new BlockWeb();
    }
    return this.onlyBlockWeb;
  }
}

BlockWeb.onlyBlockWeb = null;

/**
 * 主函数：程序运行入口函数
 */
function main() { 
  var blockWeb = BlockWeb.getSingle();
  blockWeb.startWeb();
}
// 调用主函数
main();

